Skip to content

FILT: Create MTRSimFilter that wraps the primary functionality of the MTRSim app and library#1

Open
imikejackson wants to merge 18 commits into
developfrom
topic/create_mtr_sim_filter
Open

FILT: Create MTRSimFilter that wraps the primary functionality of the MTRSim app and library#1
imikejackson wants to merge 18 commits into
developfrom
topic/create_mtr_sim_filter

Conversation

@imikejackson
Copy link
Copy Markdown
Contributor

No description provided.

imikejackson and others added 14 commits June 1, 2026 14:02
Integration of MTR representation codes into DREAM3D-NX filters:
MTRSimFilter (algorithm MTRSim, "Generate Synthetic Microtexture"),
filter-focused + statistical test strategy, and CI validation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…der)

- Random seed uses standard simplnx UseSeed/SeedValue/SeedArray pattern
- Output defaults: geometry "MTR Microstructure", arrays "MTRIds"/"Eulers"
- Clarify voxel remap target is SIMPLNX z,y,x (slowest->fastest)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TDD plan for MTRSimFilter: LibMTRSim driver (buildUniformODF,
gridToODFComponent, remapSimToZYX, simulateMTR), the filter +
algorithm, deterministic + statistical tests, and CI validation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Declares MTRSimResult struct and simulateMTR() in MTRSimDriver.hpp/.cpp.
The function consolidates PGRF assignment, per-component ODF sampling, and
per-voxel orientation assignment into a single call that returns results
already remapped into SIMPLNX z,y,x voxel order.

Refactors src/app/main.cpp to call simulateMTR() and removes the now-
redundant local buildUniformODF() from the anonymous namespace.

Adds a statistical end-to-end test (6x6 mm domain, seed=42) that verifies
volume fractions land within 0.05 of targets (0.30/0.35/0.35) and that all
output Euler angles are within their valid ranges.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…view

FIX 1: Rebuild spatialCoords in SIMPLNX z,y,x order (iz outer, iy middle,
ix inner) so row k matches sim.phi*/mtrIndex[k]. The old loop used z-x-y
order which mismatched coordinates with orientations for non-square grids.

FIX 2: Replace magic literals 72, 36, 72 in the simulateMTR call with
named constants (k_OdfBinsPhi1/PHI/Phi2) documenting the fixed 5-degree
Bunge-Euler MATLAB ODF grid layout (186624 bins).

FIX 3: Guard in simulateMTR that pgrf_result.mtrIndex.size() == N; throws
std::runtime_error if the PGRF result dimensions are inconsistent.

FIX 4: CSV loop now uses sim.nx*sim.ny*sim.nz to tie the bound to the
actual result rather than the pre-call N.

FIX 5: Add phi2 range check and phi1/phi/phi2 size-equality checks to the
statistical driver test.

FIX 6: Remove unused <numbers> include from main.cpp; ODFSampler.hpp
retained (ODFComponent is used directly).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ight validation + error tests

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add zero-spacing guard (-13006) before dim() divisions in preflightImpl
- Update Physical Size help text to document the 2D (Z=0) mode
- Change physicalSize/physicalSpacing members from std::vector<float> to
  std::vector<float32> to match what executeImpl reads from filterArgs
- Change outputs separator label to "Output Data Object(s)" (matches ReadMTRSimODFFilter)
- Remove premature #include "simplnx/DataStructure/DataArray.hpp" from stub MTRSim.cpp
- Add preflight test for Theta List rows with wrong column count (-13005)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… test

Wire MTRSimFilter to the LibMTRSim simulateMTR entry point: reconstruct
ODFComponents from the selected Float64 cell arrays, build SimulationParams
from filter inputs, run the simulation, and write MTRIds + Eulers into the
output geometry cell AttributeMatrix. Polar coloring (Task 8) is deferred.

Add preflight non-negativity/range hardening (error -13007) requiring each
Volume Fraction value to lie in [0, 1].

Add an end-to-end execute test (100x100 = 10000 voxels, seed 42, 3 components)
that verifies array contract (types/components/tuples), id set {1,2,3},
finite Bunge-bounded Eulers, recorded seed, and loose volume fractions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Remove params.seed = m_InputValues->seed (dead write; rng is seeded directly)
- Add clarifying comment noting simulateMTR uses the rng, not SimulationParams::seed
- Remove unused #include <cmath> from MTRSim.cpp
- Move per-element VF range check (-13007) before sum check (-13003) so
  out-of-range values produce the precise diagnostic rather than the misleading
  sum message
- Remove redundant size/spacing/VF args.insertOrAssign calls in execute test
  that duplicated what MakeValidArgs already sets; keep seed overrides

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Implement MTRSim::applyPolarColoring using IPFMapper with the MatLab
  HCP colour scheme and Z-axis reference direction
* Call applyPolarColoring from operator() when generatePolarColoring is true;
  short-circuits on error; leaves no array when the flag is false
* Add two unit tests: polar ON verifies array shape (3 components, 10000
  tuples) and non-zero content; polar OFF asserts array is absent

Signed-off-by: Michael Jackson <mike.jackson@bluequartz.net>
Documents all parameters, output arrays, preflight error codes, the units
consistency requirement for Physical Size / Spacing / Theta List, and the
expected single-stage progress behaviour for large volumes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit

clang-format

[clang-format] reported by reviewdog 🐶

const DataPath cellAm = m_InputValues->outputGeometryPath.createChildPath(m_InputValues->cellAttrMatName);
auto& mtrIds = m_DataStructure.getDataRefAs<Int32Array>(cellAm.createChildPath(m_InputValues->mtrIdsArrayName));
auto& eulers = m_DataStructure.getDataRefAs<Float32Array>(cellAm.createChildPath(m_InputValues->eulersArrayName));
auto& mtrStore = mtrIds.getDataStoreRef();
auto& eulerStore = eulers.getDataStoreRef();


[clang-format] reported by reviewdog 🐶

for(std::size_t i = 0; i < N; ++i)
{


[clang-format] reported by reviewdog 🐶

if(m_InputValues->generatePolarColoring)
{
m_MessageHandler(IFilter::Message::Type::Info, "Computing polar coloring...");


[clang-format] reported by reviewdog 🐶

if(colorResult.invalid())
{


[clang-format] reported by reviewdog 🐶

Result<> MTRSim::applyPolarColoring(const mtrsim::MTRSimResult& sim, const DataPath& cellAttrMatPath)
{


[clang-format] reported by reviewdog 🐶

Eigen::VectorXd phi1 = Eigen::Map<const Eigen::VectorXd>(sim.phi1.data(), static_cast<Eigen::Index>(N));
Eigen::VectorXd phi = Eigen::Map<const Eigen::VectorXd>(sim.phi.data(), static_cast<Eigen::Index>(N));
Eigen::VectorXd phi2 = Eigen::Map<const Eigen::VectorXd>(sim.phi2.data(), static_cast<Eigen::Index>(N));


[clang-format] reported by reviewdog 🐶

const std::vector<mtrsim::RGBColor> colors = mapper.eulerToColors(phi1, phi, phi2, {0.0, 0.0, 1.0}, mtrsim::IPFColorScheme::MatLab);


[clang-format] reported by reviewdog 🐶

auto& rgb = m_DataStructure.getDataRefAs<UInt8Array>(cellAttrMatPath.createChildPath(m_InputValues->polarColorsArrayName));
auto& store = rgb.getDataStoreRef();
for(std::size_t i = 0; i < N; ++i)
{


[clang-format] reported by reviewdog 🐶

namespace nx::core
{


[clang-format] reported by reviewdog 🐶

struct MTRSIM_EXPORT MTRSimInputValues
{


[clang-format] reported by reviewdog 🐶

* from an input ODF and a set of simulation parameters. The output ImageGeom and
* its cell arrays are created by the filter's preflight; this algorithm fills them.


[clang-format] reported by reviewdog 🐶

class MTRSIM_EXPORT MTRSim
{


[clang-format] reported by reviewdog 🐶

MTRSim(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, MTRSimInputValues* inputValues);


[clang-format] reported by reviewdog 🐶

MTRSim(const MTRSim&) = delete;
MTRSim(MTRSim&&) noexcept = delete;
MTRSim& operator=(const MTRSim&) = delete;
MTRSim& operator=(MTRSim&&) noexcept = delete;


[clang-format] reported by reviewdog 🐶

Result<> applyPolarColoring(const mtrsim::MTRSimResult& sim, const DataPath& cellAttrMatPath);
DataStructure& m_DataStructure;
const MTRSimInputValues* m_InputValues = nullptr;
const std::atomic_bool& m_ShouldCancel;
const IFilter::MessageHandler& m_MessageHandler;


[clang-format] reported by reviewdog 🐶

namespace nx::core
{


[clang-format] reported by reviewdog 🐶

std::string MTRSimFilter::name() const
{


[clang-format] reported by reviewdog 🐶

std::string MTRSimFilter::className() const
{


[clang-format] reported by reviewdog 🐶

Uuid MTRSimFilter::uuid() const
{
return FilterTraits<MTRSimFilter>::uuid;
}


[clang-format] reported by reviewdog 🐶

std::string MTRSimFilter::humanName() const
{


[clang-format] reported by reviewdog 🐶

std::vector<std::string> MTRSimFilter::defaultTags() const
{


[clang-format] reported by reviewdog 🐶

Parameters MTRSimFilter::parameters() const
{


[clang-format] reported by reviewdog 🐶

params.insert(std::make_unique<GeometrySelectionParameter>(k_InputOdfGeometry_Key, "Input ODF Geometry", "Image Geometry holding the ODF (from the Read/Compute ODF filters).", DataPath{},
GeometrySelectionParameter::AllowedTypes{IGeometry::Type::Image}));
params.insert(std::make_unique<MultiArraySelectionParameter>(k_OdfComponentArrays_Key, "ODF Component Arrays", "Ordered list of per-component ODF cell arrays. Order maps to Volume Fraction columns.",
MultiArraySelectionParameter::ValueType{}, MultiArraySelectionParameter::AllowedTypes{IArray::ArrayType::DataArray}, GetAllNumericTypes(),
MultiArraySelectionParameter::AllowedComponentShapes{{1}}));


[clang-format] reported by reviewdog 🐶

params.insert(std::make_unique<DynamicTableParameter>(k_VolumeFractions_Key, "Volume Fraction", "One value per ODF component; must match the component count and sum to 1.0.", vfInfo));


[clang-format] reported by reviewdog 🐶

thetaInfo.setRowsInfo(DynamicTableInfo::DynamicVectorInfo(1, 2, "Gaussian {}"));
thetaInfo.setColsInfo(DynamicTableInfo::StaticVectorInfo(DynamicTableInfo::HeadersListType{"theta_x", "theta_y", "theta_z"}));
params.insert(std::make_unique<DynamicTableParameter>(k_ThetaList_Key, "Theta List",
"Correlation lengths [theta_x, theta_y, theta_z] per latent Gaussian. Needs >= (components - 1) rows. Same length unit as Physical "
"Size/Spacing.",
thetaInfo));


[clang-format] reported by reviewdog 🐶

params.insert(std::make_unique<VectorFloat32Parameter>(k_PhysicalSize_Key, "Physical Size (microns)", "Domain extent X, Y, Z in microns. Set Z = 0 to generate a single-layer (2D) microstructure.", std::vector<float32>{38.1f, 12.7f, 0.0f},
std::vector<std::string>{"X", "Y", "Z"}));
params.insert(std::make_unique<VectorFloat32Parameter>(k_PhysicalSpacing_Key, "Physical Spacing (microns)", "Voxel spacing X,Y,Z.", std::vector<float32>{0.02f, 0.02f, 0.02f},
std::vector<std::string>{"X", "Y", "Z"}));
params.insertSeparator(Parameters::Separator{"Random Number Seed Parameters"});
params.insertLinkableParameter(std::make_unique<BoolParameter>(k_UseSeed_Key, "Use Seed for Random Generation", "When true the user can supply a fixed seed.", false));
params.insert(std::make_unique<NumberParameter<uint64>>(k_SeedValue_Key, "Seed Value", "The seed fed into the random generator.", std::mt19937::default_seed));
params.insert(std::make_unique<DataObjectNameParameter>(k_SeedArrayName_Key, "Stored Seed Value Array Name", "Top-level array recording the seed used.", "MTRSim SeedValue"));


[clang-format] reported by reviewdog 🐶

params.insertLinkableParameter(
std::make_unique<BoolParameter>(k_GeneratePolarColoring_Key, "Generate Polar Coloring", "Create a 3-component UInt8 RGB array using the MATLAB polar color mapping.", false));
params.insert(std::make_unique<DataGroupCreationParameter>(k_OutputGeometry_Key, "Output Image Geometry", "Path of the new microstructure Image Geometry.", DataPath({"MTR Microstructure"})));
params.insert(std::make_unique<DataObjectNameParameter>(k_CellAttrMatName_Key, "Cell Attribute Matrix Name", "Name of the created cell AttributeMatrix.", "Cell Data"));
params.insert(std::make_unique<DataObjectNameParameter>(k_MtrIdsArrayName_Key, "MTR Ids Array Name", "Int32 per-voxel MTR component id (1-based).", "MTRIds"));
params.insert(std::make_unique<DataObjectNameParameter>(k_EulersArrayName_Key, "Euler Angles Array Name", "Float32 3-component Bunge Euler angles [rad].", "Eulers"));
params.insert(std::make_unique<DataObjectNameParameter>(k_PolarColorsArrayName_Key, "Polar Colors Array Name", "UInt8 3-component RGB polar coloring.", "Polar Colors"));


[clang-format] reported by reviewdog 🐶

params.linkParameters(k_GeneratePolarColoring_Key, k_PolarColorsArrayName_Key, true);


[clang-format] reported by reviewdog 🐶

IFilter::VersionType MTRSimFilter::parametersVersion() const
{
return 1;
}


[clang-format] reported by reviewdog 🐶

IFilter::UniquePointer MTRSimFilter::clone() const
{


[clang-format] reported by reviewdog 🐶

IFilter::PreflightResult MTRSimFilter::preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, const std::atomic_bool& shouldCancel,
const ExecutionContext& executionContext) const
{
auto pOdfArrays = filterArgs.value<MultiArraySelectionParameter::ValueType>(k_OdfComponentArrays_Key);
auto pVolumeFractions = filterArgs.value<DynamicTableParameter::ValueType>(k_VolumeFractions_Key);
auto pThetaList = filterArgs.value<DynamicTableParameter::ValueType>(k_ThetaList_Key);


[clang-format] reported by reviewdog 🐶

if(numComponents < 2)
{
return {MakeErrorResult<OutputActions>(-13001, "MTRSim requires at least 2 ODF component arrays.")};


[clang-format] reported by reviewdog 🐶

if(pVolumeFractions.size() != 1 || pVolumeFractions[0].size() != numComponents)
{
return {MakeErrorResult<OutputActions>(-13002, fmt::format("Volume Fraction must be 1 row x {} columns (one per ODF component).", numComponents))};


[clang-format] reported by reviewdog 🐶

for(double v : pVolumeFractions[0])
{
if(v < 0.0 || v > 1.0)
{
return {MakeErrorResult<OutputActions>(-13007, fmt::format("Each Volume Fraction value must be in the range [0, 1] (got {:.4f}).", v))};


[clang-format] reported by reviewdog 🐶

for(double v : pVolumeFractions[0])
{


[clang-format] reported by reviewdog 🐶

if(std::abs(vfSum - 1.0) > 1.0e-3)
{
return {MakeErrorResult<OutputActions>(-13003, fmt::format("Volume Fraction values must sum to 1.0 (got {:.4f}).", vfSum))};


[clang-format] reported by reviewdog 🐶

if(pThetaList.size() < numComponents - 1)
{
return {MakeErrorResult<OutputActions>(-13004, fmt::format("Theta List needs at least {} rows (components - 1).", numComponents - 1))};


[clang-format] reported by reviewdog 🐶

for(const auto& row : pThetaList)
{
if(row.size() != 3)
{
return {MakeErrorResult<OutputActions>(-13005, "Each Theta List row must have exactly 3 columns.")};


[clang-format] reported by reviewdog 🐶

if(pSpacing[0] <= 0.0f || pSpacing[1] <= 0.0f)
{
return {MakeErrorResult<OutputActions>(-13006, "Physical Spacing X and Y must be greater than 0.")};


[clang-format] reported by reviewdog 🐶

const auto dim = [](float len, float sp) { return static_cast<usize>(std::max(std::lround(len / sp), 1L)); };


[clang-format] reported by reviewdog 🐶

const std::vector<float32> spacingXYZ = {pSpacing[0], pSpacing[1], pSpacing[2]};


[clang-format] reported by reviewdog 🐶

resultOutputActions.value().appendAction(std::make_unique<CreateImageGeometryAction>(pOutGeomPath, imageGeomDimsXYZ, origin, spacingXYZ, pCellAttrMatName));
const DataPath cellAttrMatPath = pOutGeomPath.createChildPath(pCellAttrMatName);
resultOutputActions.value().appendAction(std::make_unique<CreateArrayAction>(DataType::int32, tupleShapeZYX, std::vector<usize>{1}, cellAttrMatPath.createChildPath(pMtrIdsName)));
resultOutputActions.value().appendAction(std::make_unique<CreateArrayAction>(DataType::float32, tupleShapeZYX, std::vector<usize>{3}, cellAttrMatPath.createChildPath(pEulersName)));
if(pGenPolar)
{
resultOutputActions.value().appendAction(std::make_unique<CreateArrayAction>(DataType::uint8, tupleShapeZYX, std::vector<usize>{3}, cellAttrMatPath.createChildPath(pPolarName)));


[clang-format] reported by reviewdog 🐶

resultOutputActions.value().appendAction(std::make_unique<CreateArrayAction>(DataType::uint64, std::vector<usize>{1}, std::vector<usize>{1}, DataPath({pSeedArrayName})));


[clang-format] reported by reviewdog 🐶

preflightUpdatedValues.push_back({"Output Grid (X, Y, Z)", fmt::format("{} x {} x {}", nx, ny, nz)});
preflightUpdatedValues.push_back({"Number of ODF Components", std::to_string(numComponents)});


[clang-format] reported by reviewdog 🐶

Result<> MTRSimFilter::executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, const std::atomic_bool& shouldCancel,
const ExecutionContext& executionContext) const
{


[clang-format] reported by reviewdog 🐶

inputValues.inputOdfGeometryPath = filterArgs.value<DataPath>(k_InputOdfGeometry_Key);
inputValues.odfComponentPaths = filterArgs.value<MultiArraySelectionParameter::ValueType>(k_OdfComponentArrays_Key);
inputValues.volumeFractions = filterArgs.value<DynamicTableParameter::ValueType>(k_VolumeFractions_Key);
inputValues.thetaList = filterArgs.value<DynamicTableParameter::ValueType>(k_ThetaList_Key);
inputValues.physicalSize = filterArgs.value<std::vector<float32>>(k_PhysicalSize_Key);
inputValues.physicalSpacing = filterArgs.value<std::vector<float32>>(k_PhysicalSpacing_Key);
inputValues.generatePolarColoring = filterArgs.value<bool>(k_GeneratePolarColoring_Key);
inputValues.outputGeometryPath = filterArgs.value<DataPath>(k_OutputGeometry_Key);
inputValues.cellAttrMatName = filterArgs.value<std::string>(k_CellAttrMatName_Key);
inputValues.mtrIdsArrayName = filterArgs.value<std::string>(k_MtrIdsArrayName_Key);
inputValues.eulersArrayName = filterArgs.value<std::string>(k_EulersArrayName_Key);
inputValues.polarColorsArrayName = filterArgs.value<std::string>(k_PolarColorsArrayName_Key);


[clang-format] reported by reviewdog 🐶

if(!filterArgs.value<bool>(k_UseSeed_Key))
{
seed = static_cast<uint64>(std::chrono::steady_clock::now().time_since_epoch().count());


[clang-format] reported by reviewdog 🐶

dataStructure.getDataRefAs<UInt64Array>(DataPath({filterArgs.value<std::string>(k_SeedArrayName_Key)}))[0] = seed;


[clang-format] reported by reviewdog 🐶

Result<Arguments> MTRSimFilter::FromSIMPLJson(const nlohmann::json& json)
{


[clang-format] reported by reviewdog 🐶

return ConvertResultTo<Arguments>(std::move(conversionResult), std::move(args));


[clang-format] reported by reviewdog 🐶

namespace nx::core
{


[clang-format] reported by reviewdog 🐶

class MTRSIM_EXPORT MTRSimFilter : public IFilter
{


[clang-format] reported by reviewdog 🐶

MTRSimFilter(const MTRSimFilter&) = delete;
MTRSimFilter(MTRSimFilter&&) noexcept = delete;


[clang-format] reported by reviewdog 🐶

MTRSimFilter& operator=(const MTRSimFilter&) = delete;
MTRSimFilter& operator=(MTRSimFilter&&) noexcept = delete;


[clang-format] reported by reviewdog 🐶

static inline constexpr StringLiteral k_InputOdfGeometry_Key = "input_odf_geometry";
static inline constexpr StringLiteral k_OdfComponentArrays_Key = "odf_component_arrays";
static inline constexpr StringLiteral k_VolumeFractions_Key = "volume_fractions";


[clang-format] reported by reviewdog 🐶

static inline constexpr StringLiteral k_PhysicalSpacing_Key = "physical_spacing";


[clang-format] reported by reviewdog 🐶

static inline constexpr StringLiteral k_GeneratePolarColoring_Key = "generate_polar_coloring";
static inline constexpr StringLiteral k_OutputGeometry_Key = "output_geometry";
static inline constexpr StringLiteral k_CellAttrMatName_Key = "cell_attribute_matrix_name";
static inline constexpr StringLiteral k_MtrIdsArrayName_Key = "mtr_ids_array_name";
static inline constexpr StringLiteral k_EulersArrayName_Key = "eulers_array_name";
static inline constexpr StringLiteral k_PolarColorsArrayName_Key = "polar_colors_array_name";


[clang-format] reported by reviewdog 🐶

static Result<Arguments> FromSIMPLJson(const nlohmann::json& json);


[clang-format] reported by reviewdog 🐶

* @brief Takes in a DataStructure and checks that the filter can be run on it with the given arguments.
* Returns any warnings/errors. Also returns the changes that would be applied to the DataStructure.
* Some parts of the actions may not be completely filled out if all the required information is not available at preflight time.


[clang-format] reported by reviewdog 🐶

* @param filterArgs These are the input values for each parameter that is required for the filter


[clang-format] reported by reviewdog 🐶

* @param shouldCancel Atomic boolean value that can be checked to cancel the filter
* @param executionContext The ExecutionContext that can be used to determine the correct absolute path from a relative path
* @return Returns a Result object with error or warning values if any of those occurred during execution of this function


[clang-format] reported by reviewdog 🐶

PreflightResult preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, const std::atomic_bool& shouldCancel,
const ExecutionContext& executionContext) const override;


[clang-format] reported by reviewdog 🐶

* @brief Applies the filter's algorithm to the DataStructure with the given arguments. Returns any warnings/errors.
* On failure, there is no guarantee that the DataStructure is in a correct state.


[clang-format] reported by reviewdog 🐶

* @param filterArgs These are the input values for each parameter that is required for the filter


[clang-format] reported by reviewdog 🐶

* @param shouldCancel Atomic boolean value that can be checked to cancel the filter
* @param executionContext The ExecutionContext that can be used to determine the correct absolute path from a relative path
* @return Returns a Result object with error or warning values if any of those occurred during execution of this function


[clang-format] reported by reviewdog 🐶

Result<> executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, const std::atomic_bool& shouldCancel,
const ExecutionContext& executionContext) const override;


[clang-format] reported by reviewdog 🐶

SIMPLNX_DEF_FILTER_TRAITS(nx::core, MTRSimFilter, "f7f7a330-4bff-4a42-a573-09117a89a0a0");


[clang-format] reported by reviewdog 🐶

mtrsim::MTRSimResult sim = mtrsim::simulateMTR(params, odfComponents, rng, k_OdfBinsPhi1, k_OdfBinsPHI, k_OdfBinsPhi2);


[clang-format] reported by reviewdog 🐶

MTRSim/src/app/main.cpp

Lines 252 to 254 in 4f5504c

Eigen::VectorXd phi1Vec = Eigen::Map<Eigen::VectorXd>(sim.phi1.data(), static_cast<Eigen::Index>(sim.phi1.size()));
Eigen::VectorXd phiVec = Eigen::Map<Eigen::VectorXd>(sim.phi.data(), static_cast<Eigen::Index>(sim.phi.size()));
Eigen::VectorXd phi2Vec = Eigen::Map<Eigen::VectorXd>(sim.phi2.data(), static_cast<Eigen::Index>(sim.phi2.size()));


[clang-format] reported by reviewdog 🐶

<< phi2Vec[i] << ',' << sim.mtrIndex[static_cast<std::size_t>(i)] << '\n';


[clang-format] reported by reviewdog 🐶

* 5. Theta List rows with wrong column count (2 instead of 3) -> invalid (-13005).


[clang-format] reported by reviewdog 🐶

namespace
{


[clang-format] reported by reviewdog 🐶

std::vector<DataPath> BuildOdfDataStructure(DataStructure& dataStructure, usize numComponents)
{
ImageGeom* imageGeom = ImageGeom::Create(dataStructure, k_OdfGeomPath.getTargetName());


[clang-format] reported by reviewdog 🐶

imageGeom->setDimensions({k_NPhi2, k_NPHI, k_NPhi1}); // X(phi2), Y(PHI), Z(phi1)


[clang-format] reported by reviewdog 🐶

AttributeMatrix* cellAM = AttributeMatrix::Create(dataStructure, k_CellAttrMatName, tupleShape, imageGeom->getId());


[clang-format] reported by reviewdog 🐶

const DataPath cellAttrMatPath = k_OdfGeomPath.createChildPath(k_CellAttrMatName);
for(usize c = 0; c < numComponents; ++c)
{


[clang-format] reported by reviewdog 🐶

CreateTestDataArray<float64>(dataStructure, name, tupleShape, {1}, cellAM->getId());


[clang-format] reported by reviewdog 🐶

Arguments MakeValidArgs(const std::vector<DataPath>& compPaths)
{


[clang-format] reported by reviewdog 🐶

args.insertOrAssign(MTRSimFilter::k_VolumeFractions_Key, DynamicTableParameter::ValueType{{0.30, 0.35, 0.35}});
args.insertOrAssign(MTRSimFilter::k_ThetaList_Key, DynamicTableParameter::ValueType{{0.1, 0.45, 0.1}, {0.08, 0.37, 0.08}});
args.insertOrAssign(MTRSimFilter::k_PhysicalSize_Key, std::vector<float32>{2.0f, 2.0f, 0.0f});
args.insertOrAssign(MTRSimFilter::k_PhysicalSpacing_Key, std::vector<float32>{0.02f, 0.02f, 0.02f});


[clang-format] reported by reviewdog 🐶

args.insertOrAssign(MTRSimFilter::k_SeedArrayName_Key, std::string("MTRSim SeedValue"));


[clang-format] reported by reviewdog 🐶

args.insertOrAssign(MTRSimFilter::k_OutputGeometry_Key, DataPath({"MTR Microstructure"}));
args.insertOrAssign(MTRSimFilter::k_CellAttrMatName_Key, std::string("Cell Data"));
args.insertOrAssign(MTRSimFilter::k_MtrIdsArrayName_Key, std::string("MTRIds"));
args.insertOrAssign(MTRSimFilter::k_EulersArrayName_Key, std::string("Eulers"));
args.insertOrAssign(MTRSimFilter::k_PolarColorsArrayName_Key, std::string("Polar Colors"));


[clang-format] reported by reviewdog 🐶

TEST_CASE("MTRSim::MTRSimFilter: Valid preflight builds actions", "[MTRSim][MTRSimFilter]")
{


[clang-format] reported by reviewdog 🐶

const std::vector<DataPath> compPaths = BuildOdfDataStructure(dataStructure, 3);


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 105 to 106 in 4f5504c

TEST_CASE("MTRSim::MTRSimFilter: Rejects mismatched Volume Fraction column count", "[MTRSim][MTRSimFilter][ErrorPath]")
{


[clang-format] reported by reviewdog 🐶

const std::vector<DataPath> compPaths = BuildOdfDataStructure(dataStructure, 3);


[clang-format] reported by reviewdog 🐶

args.insertOrAssign(MTRSimFilter::k_VolumeFractions_Key, DynamicTableParameter::ValueType{{0.5, 0.5}});


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 121 to 122 in 4f5504c

TEST_CASE("MTRSim::MTRSimFilter: Execute wires simulation to output arrays", "[MTRSim][MTRSimFilter]")
{


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 126 to 130 in 4f5504c

const std::vector<DataPath> compPaths = BuildOdfDataStructure(dataStructure, 3);
// Fill every component array with a uniform value so ODF sampling is well-defined.
for(const auto& path : compPaths)
{
auto& arr = dataStructure.getDataRefAs<Float64Array>(path);


[clang-format] reported by reviewdog 🐶

auto& mtrIds = dataStructure.getDataRefAs<Int32Array>(cellAm.createChildPath("MTRIds"));


[clang-format] reported by reviewdog 🐶

auto& eulers = dataStructure.getDataRefAs<Float32Array>(cellAm.createChildPath("Eulers"));


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 163 to 165 in 4f5504c

// MTR ids in {1,2,3}; at least 2 distinct ids appear. Also accumulate empirical
// volume fractions for a loose wiring check.
const auto& mtrStore = mtrIds.getDataStoreRef();


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 167 to 168 in 4f5504c

for(usize i = 0; i < mtrStore.getSize(); ++i)
{


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 175 to 178 in 4f5504c

for(usize id = 1; id <= 3; ++id)
{
if(counts[id] > 0)
{


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 187 to 189 in 4f5504c

const auto& eulerStore = eulers.getDataStoreRef();
for(usize t = 0; t < expectedTuples; ++t)
{


[clang-format] reported by reviewdog 🐶

auto& seedArray = dataStructure.getDataRefAs<UInt64Array>(DataPath({"MTRSim SeedValue"}));


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 212 to 214 in 4f5504c

for(usize id = 1; id <= 3; ++id)
{
const double empirical = static_cast<double>(counts[id]) / static_cast<double>(expectedTuples);


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 219 to 220 in 4f5504c

TEST_CASE("MTRSim::MTRSimFilter: Rejects too few Theta List rows", "[MTRSim][MTRSimFilter][ErrorPath]")
{


[clang-format] reported by reviewdog 🐶

const std::vector<DataPath> compPaths = BuildOdfDataStructure(dataStructure, 3);


[clang-format] reported by reviewdog 🐶

args.insertOrAssign(MTRSimFilter::k_ThetaList_Key, DynamicTableParameter::ValueType{{0.1, 0.45, 0.1}});


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 235 to 236 in 4f5504c

TEST_CASE("MTRSim::MTRSimFilter: Rejects Volume Fraction not summing to 1.0", "[MTRSim][MTRSimFilter][ErrorPath]")
{


[clang-format] reported by reviewdog 🐶

const std::vector<DataPath> compPaths = BuildOdfDataStructure(dataStructure, 3);


[clang-format] reported by reviewdog 🐶

args.insertOrAssign(MTRSimFilter::k_VolumeFractions_Key, DynamicTableParameter::ValueType{{0.2, 0.2, 0.2}});


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 251 to 252 in 4f5504c

TEST_CASE("MTRSim::MTRSimFilter: Execute with polar coloring ON fills Polar Colors array", "[MTRSim][MTRSimFilter]")
{


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 256 to 259 in 4f5504c

const std::vector<DataPath> compPaths = BuildOdfDataStructure(dataStructure, 3);
for(const auto& path : compPaths)
{
auto& arr = dataStructure.getDataRefAs<Float64Array>(path);


[clang-format] reported by reviewdog 🐶

const DataPath cellAm = DataPath({"MTR Microstructure"}).createChildPath("Cell Data");


[clang-format] reported by reviewdog 🐶

auto& rgb = dataStructure.getDataRefAs<UInt8Array>(cellAm.createChildPath("Polar Colors"));


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 282 to 283 in 4f5504c

for(usize i = 0; i < rgb.getSize(); ++i)
{


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 289 to 290 in 4f5504c

TEST_CASE("MTRSim::MTRSimFilter: Execute with polar coloring OFF omits Polar Colors array", "[MTRSim][MTRSimFilter]")
{


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 294 to 297 in 4f5504c

const std::vector<DataPath> compPaths = BuildOdfDataStructure(dataStructure, 3);
for(const auto& path : compPaths)
{
auto& arr = dataStructure.getDataRefAs<Float64Array>(path);


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 313 to 314 in 4f5504c

const DataPath cellAm = DataPath({"MTR Microstructure"}).createChildPath("Cell Data");
REQUIRE(dataStructure.getDataAs<UInt8Array>(cellAm.createChildPath("Polar Colors")) == nullptr);


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 317 to 318 in 4f5504c

TEST_CASE("MTRSim::MTRSimFilter: Rejects Theta List rows with wrong column count", "[MTRSim][MTRSimFilter][ErrorPath]")
{


[clang-format] reported by reviewdog 🐶

const std::vector<DataPath> compPaths = BuildOdfDataStructure(dataStructure, 3);


[clang-format] reported by reviewdog 🐶

MTRSim/test/MTRSimTest.cpp

Lines 326 to 329 in 4f5504c

// 2 rows supplied (enough for 3 components: needs >= 2), but each row has only 2
// columns instead of 3 — must trigger the column-count check (-13005), not the
// row-count check (-13004).
args.insertOrAssign(MTRSimFilter::k_ThetaList_Key, DynamicTableParameter::ValueType{{0.1, 0.45}, {0.08, 0.37}});


[clang-format] reported by reviewdog 🐶

TEST_CASE("gridToODFComponent derives bin centres in radians and normalizes", "[mtrsim_driver]") {


[clang-format] reported by reviewdog 🐶

TEST_CASE("remapSimToZYX moves y-fastest data to x-fastest layout", "[mtrsim_driver]") {


[clang-format] reported by reviewdog 🐶

for (int i = 0; i < nx * ny * nz; ++i) { in[i] = i; }


[clang-format] reported by reviewdog 🐶

TEST_CASE("simulateMTR reproduces target volume fractions (statistical)", "[mtrsim_driver][statistical]") {


[clang-format] reported by reviewdog 🐶

params.xLen = 6.0; params.yLen = 6.0; params.zLen = 0.0;
params.dx = 0.02; params.dy = 0.02; params.dz = 0.02;


[clang-format] reported by reviewdog 🐶

mtrsim::buildUniformODF(72, 36, 72),
mtrsim::buildUniformODF(72, 36, 72),


[clang-format] reported by reviewdog 🐶

const mtrsim::MTRSimResult r = mtrsim::simulateMTR(params, comps, rng, 72, 36, 72);


[clang-format] reported by reviewdog 🐶

for (int v : r.mtrIndex) { REQUIRE(v >= 1); REQUIRE(v <= 3); }


[clang-format] reported by reviewdog 🐶

for (int v : r.mtrIndex) { counts[static_cast<std::size_t>(v - 1)]++; }


[clang-format] reported by reviewdog 🐶

for (double a : r.phi1) { REQUIRE(a >= 0.0); REQUIRE(a <= 2.0 * std::numbers::pi); }
for (double a : r.phi) { REQUIRE(a >= 0.0); REQUIRE(a <= std::numbers::pi); }
for (double a : r.phi2) { REQUIRE(a >= 0.0); REQUIRE(a <= 2.0 * std::numbers::pi); }

@imikejackson imikejackson force-pushed the topic/create_mtr_sim_filter branch from 4f5504c to 125eab7 Compare June 1, 2026 20:34
Comment thread tests/test_mtrsim_driver.cpp Outdated
Comment thread tests/test_mtrsim_driver.cpp Outdated
Comment thread tests/test_mtrsim_driver.cpp Outdated
Comment thread tests/test_mtrsim_driver.cpp Outdated
Comment thread tests/test_mtrsim_driver.cpp Outdated
Comment thread tests/test_mtrsim_driver.cpp Outdated
Comment thread tests/test_mtrsim_driver.cpp
Comment thread tests/test_mtrsim_driver.cpp Outdated
Comment thread tests/test_mtrsim_driver.cpp Outdated
Comment thread tests/test_mtrsim_driver.cpp Outdated
@imikejackson imikejackson force-pushed the topic/create_mtr_sim_filter branch from 125eab7 to f5aabb0 Compare June 1, 2026 20:41
Plan updated with the Approx (not Catch::Approx) and
src/LibMTRSim/CMakeLists.txt corrections discovered during execution.
@imikejackson imikejackson force-pushed the topic/create_mtr_sim_filter branch from f5aabb0 to dea7856 Compare June 1, 2026 20:49
Embed the conceptual "what it generates" and "how it works" slides
(converted from output/avatar_slides) plus an ODF Euler-space figure into
the filter documentation. Also correct the Execute error code in the table
to -13050 to match the algorithm source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@imikejackson imikejackson force-pushed the topic/create_mtr_sim_filter branch from 10bac67 to 78e6d53 Compare June 1, 2026 21:04
imikejackson and others added 2 commits June 2, 2026 07:49
…dationTest)

simplnx's FilterValidationTest requires path-type parameter keys to end
with '_path' and ChoicesParameter keys with '_index'. Rename the offending
key strings across MTRSimFilter, ComputeODFFilter, ReadMTRSimODFFilter, and
WriteMTRSimODFFilter (C++ constant names unchanged, so .cpp/tests are
unaffected) and update the avtr12 example pipeline to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant